Transcrição
É importante entendermos que, à medida em que digitamos no campo de busca no navegador, o evento keyup está sendo disparado. Da mesma forma, o debounce é executado, porém não há nada inscrito nele. Antes, realizávamos o subscribe() passando o termo buscado à propriedade filter de PhotoListComponent, para filtrarmos a lista.
Entretanto, desta vez estamos em um componente, e o valor filter precisa chegar a outro. O ngOnInit() de search.component.ts ficará assim:
ngOnInit(): void {
this.debounce
.pipe(debounceTime(300))
.subscribe(filter => console.log(filter));
}No navegador, conforme vamos digitando o termo que queremos buscar e consultamos o console, confirmaremos que os dados estão sendo armazenados corretamente. Precisamos fazer com que o valor de console, neste componente específico, de alguma forma caia em filter de PhotoListComponent.
Já aprendemos a trabalhar com a inbound property, então, na tag <ap-search> de photo-list.component.html, teremos um evento customizado ("custom event") chamado onTyping. Trata-se de um evento do componente que nós definiremos da maneira que preferirmos. Sabemos que para fazermos um Data binding de eventos é necessário colocarmos a propriedade entre parênteses, então indicaremos que neste <ap-search> que irá disparar o evento onTyping, filter receberá $event.
Com isso, informamos que, conforme vamos digitando no campo de busca, o componente irá disparar este evento cujo $event será o input digitado, o qual será levado a filter de PhotoListComponent. Para criarmos um evento customizado, em search.component.ts adicionaremos um @Output() chamado onTyping, mesmo nome que colocamos no evento. Além disso, precisamos indicar que @Output() é um new EventEmitter, tomando cuidado para que isto não seja importado do pacote errado — neste caso, queremos que seja do próprio Angular.
Podemos definir um tipo genérico em EventEmitter(), e como usaremos um valor que é um texto, o evento emitirá um valor de tipo string.
export class SearchComponent implements OnInit, OnDestroy {
@Output() onTyping = new EventEmitter<string>();
debounce: Subject<string> = new Subject<string>();
// código omitido
}
O TypeScript automaticamente infere o tipo, porém é possível enfatizar isso usando dois pontos seguido de
EventEmitter<string>, o que não faremos neste caso.
Salvaremos as alterações e, no subscribe() de ngOnInit(), trocaremos console.log(filter) por this.onTyping.emit(filter), pois toda vez que passarmos pelo debounce o filter será gerado:
ngOnInit(): void {
this.debounce
.pipe(debounceTime(300))
.subscribe(filter => this.onTyping.emit(filter));
}Com isso, a instrução filter => this.onTyping.emit(filter) só será pausada quando atingirmos 300ms sem digitarmos nada no campo de busca. Daí, será pego o valor digitado (filter), e o evento onTyping será emitido com o mesmo valor. Em photo-list.component.html, o $event (que nós programamos) é o valor que passamos pelo filter. Vamos salvar tudo e conferir seu funcionamento no navegador.
Desta vez, o filtro funciona corretamente! Aprendemos a lidar com a comunicação entre um elemento filho e um elemento pai. E podemos trabalhar com variações. Porém, há algo que pode confundir o usuário: após a busca e filtragem de imagens que correspondem a "farol", por exemplo, o clique no botão "Load more" não acarretará em nada.
Isso porque o filtro está em ação, e o usuário não saberá se as imagens estão sendo carregadas com sucesso, ou não. Podemos convencionar que, ao clique de "Load more", o filtro deverá ser limpo e as imagens serão recarregadas. Sendo assim, voltaremos a photo-list.component.ts e, em load(), com a chegada de novos dados, indicaremos que filter recebe uma string em branco:
load() {
this.photoService
.listFromUserPaginated(this.userName, ++this.currentPage)
.subscribe(photos => {
this.filter = '';
this.photos = this.photos.concat(photos);
if(!photos.length) this.hasMore = false;
});
}
E então retornaremos ao navegador e repetiremos a ação de digitarmos "farol" e clicarmos em "Load more". Isto fará com que as imagens sejam recarregadas e o filtro seja limpado, porém o componente de busca não está ciente disso, portanto não é atualizado. Uma das formas de resolvermos isso é incluir @Input() com valor de tipo string:
@Output() onTyping = new EventEmitter<string>();
@Input() value: string = '';
debounce: Subject<string> = new Subject<string>();Em photo-list.component.html, faremos o Data binding com filter para que ele seja limpo:
<ap-search
(onTyping)="filter = $event" [value]="filter">
</ap-search>E em search.component.html, incluiremos a linha [value]="value" dentro da tag <input>. Salvaremos tudo, recarregaremos a página no navegador. Atingimos nosso objetivo, e esta é uma das maneiras de conseguirmos lidar com a comunicação entre componentes que possuem alguma hierarquia entre si.